package top.whysu.gps.utils;

import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 多线程锁
 */
public class LockUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(LockUtil.class);

    /**
     * 临时参数
     */
    private Map<String, JSONObject> paramsPool;

    /**
     * String:key JSONObject: timestamp,params
     */
    private Map<String, JSONObject> lockPool;

    /**
     * 锁生命周期:毫秒
     */
    private long liveLock = 180000;

    /**
     * 参数生命周期:毫秒
     */
    private long liveParams = 30000;

    /**
     * 定时检查周期
     */
    private int t = 30;

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);

    private LockUtil() {
        checkLockPool();
        checkParamPool();
    }

    /**
     * 使用静态内部类创建单例对象
     */
    private static class SingletonClassInstance {
        private static final LockUtil INSTANCE = new LockUtil();
    }

    /**
     * 对外提供一个公共的访问接口
     *
     * @return
     */
    public static LockUtil getInstance() {
        return SingletonClassInstance.INSTANCE;
    }

    private void checkLockPool() {
        scheduledThreadPool.scheduleAtFixedRate((() -> {
            long now = System.currentTimeMillis();
            for (Entry entry : getLockPool().entrySet()) {
                String key = (String) entry.getKey();
                JSONObject obj = (JSONObject) entry.getValue();
                long timestamp = obj.getLongValue("timestamp");
                long diff = now - timestamp;
                if (diff > liveLock) {
                    getLockPool().remove(key);
                    LOGGER.info("当前锁生命周期超过{}毫秒，移除！", liveLock);
                }
            }
        }), 0, t, TimeUnit.SECONDS);
    }

    private void checkParamPool() {
        scheduledThreadPool.scheduleAtFixedRate((() -> {
            long now = System.currentTimeMillis();
            for (Entry entry : getParamsPool().entrySet()) {
                String key = (String) entry.getKey();
                JSONObject obj = (JSONObject) entry.getValue();
                long timestamp = obj.getLongValue("timestamp");
                long diff = now - timestamp;
                if (diff > liveParams) {
                    getParamsPool().remove(key);
                    LOGGER.info("当前参数生命周期超过{}毫秒，移除！", liveParams);
                }
            }
        }), 0, t, TimeUnit.SECONDS);
    }

    private Map<String, JSONObject> getLockPool() {
        if (lockPool == null) {
            lockPool = new ConcurrentHashMap<>();
        }
        return lockPool;
    }

    private void putLock2Pool(String key, Object lock) {
        JSONObject obj = new JSONObject();
        obj.put("timestamp", System.currentTimeMillis());
        if (lock != null) {
            obj.put("lock", lock);
        }
        getLockPool().put(key, obj);
    }

    private Object getLock(String key) {
        JSONObject obj = getLockPool().get(key);
        if (obj != null) {
            return obj.get("lock");
        }
        return null;
    }

    private void removeLock(String key) {
        getLockPool().remove(key);
    }

    private Map<String, JSONObject> getParamsPool() {
        if (paramsPool == null) {
            paramsPool = new ConcurrentHashMap<>();
        }
        return paramsPool;
    }

    private void putParam2Pool(String key, Object param) {
        JSONObject obj = new JSONObject();
        obj.put("timestamp", System.currentTimeMillis());
        if (param != null) {
            obj.put("param", param);
        }
        getParamsPool().put(key, obj);
    }

    private void removeParam(String key) {
        getParamsPool().remove(key);
    }

    private Object getParam(String key) {
        JSONObject obj = getParamsPool().get(key);
        if (obj != null) {
            return obj.get("param");
        }
        return null;
    }

    public Object lock(String key, int timeout) {
        Object lock = new Object();
        Object param = null;
        putLock2Pool(key, lock);
        synchronized (lock) {
            try {
                LOGGER.info("lock...{}", key);
                lock.wait(timeout * 1000);
                param = getParam(key);
            } catch (InterruptedException e) {
                LOGGER.debug("InterruptedException ", e);
            } finally {
                removeLock(key);
                removeParam(key);
            }
        }
        LOGGER.info("param = {}", param);
        return param;
    }

    public void unlock(String key, Object param) {
        Object lock = getLock(key);
        if (lock != null) {
            synchronized (lock) {
                putParam2Pool(key, param);
                lock.notify();
                LOGGER.info("unlock...{}", key);
            }
        }
    }
}
